package ch.ethz.eai.facade;

import java.rmi.RemoteException;
import java.util.LinkedList;

import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.Address;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.ArrayOfGeocodeResult;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.Credentials;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.Geocode;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.GeocodeLocation;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.GeocodeRequest;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.GeocodeResult;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.ReverseGeocode;
import net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.ReverseGeocodeRequest;
import net.virtualearth.dev.webservices.v1.geocode.IGeocodeService_Geocode_ResponseSummaryFault_FaultMessage;
import net.virtualearth.dev.webservices.v1.geocode.IGeocodeService_ReverseGeocode_ResponseSummaryFault_FaultMessage;

import org.apache.axis2.AxisFault;

import ch.ethz.eai.data.Location;

public class GeocodeFacade {
	
	/**
	 * Geocode a location (i.e. having the address find out the coordinates).
	 * <br>
	 * <i>Important:</i> for better precision fill in the postal code otherwise the search may return 
	 * many unrelated results (which still can be filtered later based on town name) 
	 * @param loc
	 * @return
	 */
	public static Location[] geocode(Location loc) {
		LinkedList<Location> listOfLoc = new LinkedList<Location>();
		
		GeocodeServiceStub ss;
		try {
			ss = new GeocodeServiceStub();
			Credentials cred = new Credentials();
			//secret token for the BING Maps API. 
			cred.setToken("Aq2OSf2FbIWuCc7YQLKw3s2cZTYEYmPDMUpEUeTq42kXngukKqqXI7EFDslYlZ3i");
			Geocode param = new Geocode();
			GeocodeRequest req = new GeocodeRequest();
			req.setCredentials(cred);
			Address adr = new Address();
			adr.setAddressLine(loc.getStreetAddress());
			adr.setPostalTown(loc.getTown());
			adr.setPostalCode(loc.getPostalCode());
			adr.setCountryRegion(loc.getCountry());
			req.setAddress(adr);
			param.setRequest(req);
			ArrayOfGeocodeResult geoRes = ss.geocode(param).getGeocodeResult().getResults();
			GeocodeResult[] res = geoRes.getGeocodeResult();
			if (res!=null && res.length>0) {
			for (int i=0; i<res.length; i++)
				{
					double lat = res[i].getLocations().getGeocodeLocation()[0].getLatitude();
					double longt = res[i].getLocations().getGeocodeLocation()[0].getLongitude();
					Location ladd = new Location(longt, lat, res[i].getAddress().getAddressLine(), res[i].getAddress().getLocality(), res[i].getAddress().getPostalCode(), res[i].getAddress().getCountryRegion());
					if (ladd.hasAddress()) {
						listOfLoc.add(ladd);
					}
				}
			}
		} catch (AxisFault e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IGeocodeService_Geocode_ResponseSummaryFault_FaultMessage e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return listOfLoc.toArray(new Location[0]);
		
	}
	
	/**
	 * Reverse geocode a location (i.e. given the coordinates get the address).
	 * <br><i>Important: </i>if the location has no coordinates set, the result will be an empty array! No exception is raised.
	 * @param loc
	 * @return
	 */
	public static Location[] reverseGeocode(Location loc) {
		LinkedList<Location> listOfLoc = new LinkedList<Location>();
		if (loc.hasCoordinates()) {
			GeocodeServiceStub ss;
			try {
				ss = new GeocodeServiceStub();
				Credentials cred = new Credentials();
				//secret token for the BING Maps API. 
				cred.setToken("Aq2OSf2FbIWuCc7YQLKw3s2cZTYEYmPDMUpEUeTq42kXngukKqqXI7EFDslYlZ3i");
				ReverseGeocode param = new ReverseGeocode();
				ReverseGeocodeRequest req = new ReverseGeocodeRequest();
				req.setCredentials(cred);
				net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.Location lc = new net.virtualearth.dev.webservices.v1.geocode.GeocodeServiceStub.Location();
				lc.setLatitude(loc.getLatitude());
				lc.setLongitude(loc.getLongtitude());
				req.setLocation(lc);
				param.setRequest(req);
				ArrayOfGeocodeResult geoRes = ss.reverseGeocode(param).getReverseGeocodeResult().getResults();
				GeocodeResult[] res = geoRes.getGeocodeResult();
				if (res!=null) {
					for (int i=0; i<res.length; i++)
					{
						//preserve longt and latit because the search may actually correct it!
						double lat = loc.getLatitude(); 
						double longt = loc.getLongtitude();
						
						Location ladd = new Location(longt, lat, res[i].getAddress().getAddressLine(), res[i].getAddress().getLocality(), res[i].getAddress().getPostalCode(), res[i].getAddress().getCountryRegion());
						listOfLoc.add(ladd);
					}
				}
			} catch (AxisFault e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IGeocodeService_ReverseGeocode_ResponseSummaryFault_FaultMessage e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return listOfLoc.toArray(new Location[0]);
		
	}
}